import { createLogger } from '@/lib/logs/console/logger'
import type { ParameterVisibility, ToolConfig } from '@/tools/types'
import { getTool } from '@/tools/utils'

const logger = createLogger('ToolsParams')
type ToolParamDefinition = ToolConfig['params'][string]

export interface Option {
  label: string
  value: string
}

export interface ComponentCondition {
  field: string
  value: string | number | boolean | Array<string | number | boolean>
  not?: boolean
}

export interface UIComponentConfig {
  type: string
  options?: Option[]
  placeholder?: string
  password?: boolean
  condition?: ComponentCondition
  title?: string
  value?: unknown
  serviceId?: string
  requiredScopes?: string[]
  mimeType?: string
  columns?: string[]
  min?: number
  max?: number
  step?: number
  integer?: boolean
  language?: string
  generationType?: string
  acceptedTypes?: string[]
  multiple?: boolean
  multiSelect?: boolean
  maxSize?: number
  dependsOn?: string[]
}

export interface SubBlockConfig {
  id: string
  type: string
  title?: string
  options?: Option[]
  placeholder?: string
  password?: boolean
  condition?: ComponentCondition
  value?: unknown
  serviceId?: string
  requiredScopes?: string[]
  mimeType?: string
  columns?: string[]
  min?: number
  max?: number
  step?: number
  integer?: boolean
  language?: string
  generationType?: string
  acceptedTypes?: string[]
  multiple?: boolean
  maxSize?: number
  dependsOn?: string[]
}

export interface BlockConfig {
  type: string
  subBlocks?: SubBlockConfig[]
}

export interface SchemaProperty {
  type: string
  description: string
  items?: Record<string, any>
  properties?: Record<string, SchemaProperty>
  required?: string[]
}

export interface ToolSchema {
  type: 'object'
  properties: Record<string, SchemaProperty>
  required: string[]
}

export interface ValidationResult {
  valid: boolean
  missingParams: string[]
}

export interface ToolParameterConfig {
  id: string
  type: string
  required?: boolean // Required for tool execution
  visibility?: ParameterVisibility // Controls who can/must provide this parameter
  userProvided?: boolean // User filled this parameter
  description?: string
  default?: unknown
  // UI component information from block config
  uiComponent?: UIComponentConfig
}

export interface ToolWithParameters {
  toolConfig: ToolConfig
  allParameters: ToolParameterConfig[]
  userInputParameters: ToolParameterConfig[] // Parameters shown to user
  requiredParameters: ToolParameterConfig[] // Must be filled by user or LLM
  optionalParameters: ToolParameterConfig[] // Nice to have, shown to user
}

let blockConfigCache: Record<string, BlockConfig> | null = null

const workflowInputFieldsCache = new Map<
  string,
  { fields: Array<{ name: string; type: string }>; timestamp: number }
>()
const WORKFLOW_CACHE_TTL = 5 * 60 * 1000 // 5 minutes

function getBlockConfigurations(): Record<string, BlockConfig> {
  if (!blockConfigCache) {
    try {
      const { getAllBlocks } = require('@/blocks')
      const allBlocks = getAllBlocks()
      blockConfigCache = {}
      allBlocks.forEach((block: BlockConfig) => {
        blockConfigCache![block.type] = block
      })
    } catch (error) {
      console.warn('Could not load block configuration:', error)
      blockConfigCache = {}
    }
  }
  return blockConfigCache
}

/**
 * Gets all parameters for a tool, categorized by their usage
 * Also includes UI component information from block configurations
 */
export function getToolParametersConfig(
  toolId: string,
  blockType?: string
): ToolWithParameters | null {
  try {
    const toolConfig = getTool(toolId)
    if (!toolConfig) {
      logger.warn(`Tool not found: ${toolId}`)
      return null
    }

    // Validate that toolConfig has required properties
    if (!toolConfig.params || typeof toolConfig.params !== 'object') {
      logger.warn(`Tool ${toolId} has invalid params configuration`)
      return null
    }

    // Special handling for workflow_executor tool
    if (toolId === 'workflow_executor') {
      const parameters: ToolParameterConfig[] = [
        {
          id: 'workflowId',
          type: 'string',
          required: true,
          visibility: 'user-or-llm',
          description: 'The ID of the workflow to execute',
          uiComponent: {
            type: 'workflow-selector',
            placeholder: 'Select workflow to execute',
          },
        },
        {
          id: 'inputMapping',
          type: 'object',
          required: false,
          visibility: 'user-or-llm',
          description: 'Map inputs to the selected workflow',
          uiComponent: {
            type: 'workflow-input-mapper',
            title: 'Workflow Inputs',
            condition: {
              field: 'workflowId',
              value: '',
              not: true, // Show when workflowId is not empty
            },
          },
        },
      ]

      return {
        toolConfig,
        allParameters: parameters,
        userInputParameters: parameters.filter(
          (param) => param.visibility === 'user-or-llm' || param.visibility === 'user-only'
        ),
        requiredParameters: parameters.filter((param) => param.required),
        optionalParameters: parameters.filter(
          (param) => param.visibility === 'user-only' && !param.required
        ),
      }
    }

    // Get block configuration for UI component information
    let blockConfig: BlockConfig | null = null
    if (blockType) {
      const blockConfigs = getBlockConfigurations()
      blockConfig = blockConfigs[blockType] || null
    }

    // Convert tool params to our standard format with UI component info
    const allParameters: ToolParameterConfig[] = Object.entries(toolConfig.params).map(
      ([paramId, param]) => {
        const toolParam: ToolParameterConfig = {
          id: paramId,
          type: param.type,
          required: param.required ?? false,
          visibility: param.visibility ?? (param.required ? 'user-or-llm' : 'user-only'),
          description: param.description,
          default: param.default,
        }

        // Add UI component information from block config if available
        if (blockConfig) {
          // For multi-operation tools, find the subblock that matches both the parameter ID
          // and the current tool operation
          let subBlock = blockConfig.subBlocks?.find((sb: SubBlockConfig) => {
            if (sb.id !== paramId) return false

            // If there's a condition, check if it matches the current tool
            if (sb.condition && sb.condition.field === 'operation') {
              // First try exact match with full tool ID
              if (sb.condition.value === toolId) return true

              // Then try extracting operation from tool ID
              // For tools like 'google_calendar_quick_add', extract 'quick_add'
              const parts = toolId.split('_')
              if (parts.length >= 3) {
                // Join everything after the provider prefix (e.g., 'google_calendar_')
                const operation = parts.slice(2).join('_')
                if (sb.condition.value === operation) return true
              }

              // Fallback to last part only
              const operation = parts[parts.length - 1]
              return sb.condition.value === operation
            }

            // If no condition, it's a global parameter (like apiKey)
            return !sb.condition
          })

          // Fallback: if no operation-specific match, find any matching parameter
          if (!subBlock) {
            subBlock = blockConfig.subBlocks?.find((sb: SubBlockConfig) => sb.id === paramId)
          }

          // Special case: Check if this boolean parameter is part of a checkbox-list
          if (!subBlock && param.type === 'boolean' && blockConfig) {
            // Look for a checkbox-list that includes this parameter as an option
            const checkboxListBlock = blockConfig.subBlocks?.find(
              (sb: SubBlockConfig) =>
                sb.type === 'checkbox-list' &&
                Array.isArray(sb.options) &&
                sb.options.some((opt: any) => opt.id === paramId)
            )

            if (checkboxListBlock) {
              subBlock = checkboxListBlock
            }
          }

          if (subBlock) {
            toolParam.uiComponent = {
              type: subBlock.type,
              options: subBlock.options,
              placeholder: subBlock.placeholder,
              password: subBlock.password,
              condition: subBlock.condition,
              title: subBlock.title,
              value: subBlock.value,
              serviceId: subBlock.serviceId,
              requiredScopes: subBlock.requiredScopes,
              mimeType: subBlock.mimeType,
              columns: subBlock.columns,
              min: subBlock.min,
              max: subBlock.max,
              step: subBlock.step,
              integer: subBlock.integer,
              language: subBlock.language,
              generationType: subBlock.generationType,
              acceptedTypes: subBlock.acceptedTypes,
              multiple: subBlock.multiple,
              maxSize: subBlock.maxSize,
              dependsOn: subBlock.dependsOn,
            }
          }
        }

        return toolParam
      }
    )

    // Parameters that should be shown to the user for input
    const userInputParameters = allParameters.filter(
      (param) => param.visibility === 'user-or-llm' || param.visibility === 'user-only'
    )

    // Parameters that are required (must be filled by user or LLM)
    const requiredParameters = allParameters.filter((param) => param.required)

    // Parameters that are optional but can be provided by user
    const optionalParameters = allParameters.filter(
      (param) => param.visibility === 'user-only' && !param.required
    )

    return {
      toolConfig,
      allParameters,
      userInputParameters,
      requiredParameters,
      optionalParameters,
    }
  } catch (error) {
    logger.error('Error getting tool parameters config:', error)
    return null
  }
}

/**
 * Creates a tool schema for LLM with user-provided parameters excluded
 */
function buildParameterSchema(
  toolId: string,
  paramId: string,
  param: ToolParamDefinition
): SchemaProperty {
  let schemaType = param.type
  if (schemaType === 'json' || schemaType === 'any') {
    schemaType = 'object'
  }

  const propertySchema: SchemaProperty = {
    type: schemaType,
    description: param.description || '',
  }

  if (param.type === 'array' && param.items) {
    propertySchema.items = {
      ...param.items,
      ...(param.items.properties && {
        properties: { ...param.items.properties },
      }),
    }
  } else if (param.items) {
    logger.warn(`items property ignored for non-array param "${paramId}" in tool "${toolId}"`)
  }

  return propertySchema
}

export function createUserToolSchema(toolConfig: ToolConfig): ToolSchema {
  const schema: ToolSchema = {
    type: 'object',
    properties: {},
    required: [],
  }

  for (const [paramId, param] of Object.entries(toolConfig.params)) {
    const visibility = param.visibility ?? 'user-or-llm'
    if (visibility === 'hidden') {
      continue
    }

    const propertySchema = buildParameterSchema(toolConfig.id, paramId, param)
    schema.properties[paramId] = propertySchema

    if (param.required) {
      schema.required.push(paramId)
    }
  }

  return schema
}

export async function createLLMToolSchema(
  toolConfig: ToolConfig,
  userProvidedParams: Record<string, unknown>
): Promise<ToolSchema> {
  const schema: ToolSchema = {
    type: 'object',
    properties: {},
    required: [],
  }

  // Only include parameters that the LLM should/can provide
  for (const [paramId, param] of Object.entries(toolConfig.params)) {
    const isUserProvided =
      userProvidedParams[paramId] !== undefined &&
      userProvidedParams[paramId] !== null &&
      userProvidedParams[paramId] !== ''

    // Skip parameters that user has already provided
    if (isUserProvided) {
      continue
    }

    // Skip parameters that are user-only (never shown to LLM)
    if (param.visibility === 'user-only') {
      continue
    }

    // Skip hidden parameters
    if (param.visibility === 'hidden') {
      continue
    }

    // Add parameter to LLM schema
    const propertySchema = buildParameterSchema(toolConfig.id, paramId, param)

    // Special handling for workflow_executor's inputMapping parameter
    if (toolConfig.id === 'workflow_executor' && paramId === 'inputMapping') {
      const workflowId = userProvidedParams.workflowId as string
      if (workflowId) {
        await applyDynamicSchemaForWorkflow(propertySchema, workflowId)
      }
    }

    schema.properties[paramId] = propertySchema

    // Add to required if LLM must provide it and it's originally required
    if ((param.visibility === 'user-or-llm' || param.visibility === 'llm-only') && param.required) {
      schema.required.push(paramId)
    }
  }

  return schema
}

/**
 * Apply dynamic schema enrichment for workflow_executor's inputMapping parameter
 */
async function applyDynamicSchemaForWorkflow(
  propertySchema: any,
  workflowId: string
): Promise<void> {
  try {
    const workflowInputFields = await fetchWorkflowInputFields(workflowId)

    if (workflowInputFields && workflowInputFields.length > 0) {
      propertySchema.type = 'object'
      propertySchema.properties = {}
      propertySchema.required = []

      // Convert workflow input fields to JSON schema properties
      for (const field of workflowInputFields) {
        propertySchema.properties[field.name] = {
          type: field.type || 'string',
          description: `Input field: ${field.name}`,
        }
        propertySchema.required.push(field.name)
      }

      // Update description to be more specific
      propertySchema.description = `Input values for the workflow. Required fields: ${workflowInputFields.map((f) => f.name).join(', ')}`
    }
  } catch (error) {
    logger.error('Failed to fetch workflow input fields for LLM schema:', error)
  }
}

/**
 * Helper function to fetch workflow input fields with caching
 */
async function fetchWorkflowInputFields(
  workflowId: string
): Promise<Array<{ name: string; type: string }>> {
  const cached = workflowInputFieldsCache.get(workflowId)
  const now = Date.now()

  if (cached && now - cached.timestamp < WORKFLOW_CACHE_TTL) {
    return cached.fields
  }

  try {
    const { buildAuthHeaders, buildAPIUrl } = await import('@/executor/utils/http')

    const headers = await buildAuthHeaders()
    const url = buildAPIUrl(`/api/workflows/${workflowId}`)

    const response = await fetch(url.toString(), { headers })
    if (!response.ok) {
      throw new Error('Failed to fetch workflow')
    }

    const { data } = await response.json()
    if (!data?.state?.blocks) {
      return []
    }

    const blocks = data.state.blocks as Record<string, any>
    const triggerEntry = Object.entries(blocks).find(
      ([, block]) =>
        block.type === 'start_trigger' || block.type === 'input_trigger' || block.type === 'starter'
    )

    if (!triggerEntry) {
      return []
    }

    const triggerBlock = triggerEntry[1]
    const inputFormat = triggerBlock.subBlocks?.inputFormat?.value

    let fields: Array<{ name: string; type: string }> = []

    if (Array.isArray(inputFormat)) {
      fields = inputFormat
        .filter((field: any) => field.name && typeof field.name === 'string')
        .map((field: any) => ({
          name: field.name,
          type: field.type || 'string',
        }))
    }

    workflowInputFieldsCache.set(workflowId, { fields, timestamp: now })

    return fields
  } catch (error) {
    logger.error('Error fetching workflow input fields:', error)
    return []
  }
}

/**
 * Creates a complete tool schema for execution with all parameters
 */
export function createExecutionToolSchema(toolConfig: ToolConfig): ToolSchema {
  const schema: ToolSchema = {
    type: 'object',
    properties: {},
    required: [],
  }

  Object.entries(toolConfig.params).forEach(([paramId, param]) => {
    const propertySchema: any = {
      type: param.type === 'json' ? 'object' : param.type,
      description: param.description || '',
    }

    // Include items property for arrays
    if (param.type === 'array' && param.items) {
      propertySchema.items = {
        ...param.items,
        ...(param.items.properties && {
          properties: { ...param.items.properties },
        }),
      }
    } else if (param.items) {
      logger.warn(
        `items property ignored for non-array param "${paramId}" in tool "${toolConfig.id}"`
      )
    }

    schema.properties[paramId] = propertySchema

    if (param.required) {
      schema.required.push(paramId)
    }
  })

  return schema
}

/**
 * Merges user-provided parameters with LLM-generated parameters
 */
export function mergeToolParameters(
  userProvidedParams: Record<string, unknown>,
  llmGeneratedParams: Record<string, unknown>
): Record<string, unknown> {
  // User-provided parameters take precedence
  return {
    ...llmGeneratedParams,
    ...userProvidedParams,
  }
}

/**
 * Filters out user-provided parameters from tool schema for LLM
 */
export function filterSchemaForLLM(
  originalSchema: ToolSchema,
  userProvidedParams: Record<string, unknown>
): ToolSchema {
  if (!originalSchema || !originalSchema.properties) {
    return originalSchema
  }

  const filteredProperties = { ...originalSchema.properties }
  const filteredRequired = [...(originalSchema.required || [])]

  // Remove user-provided parameters from the schema
  Object.keys(userProvidedParams).forEach((paramKey) => {
    if (
      userProvidedParams[paramKey] !== undefined &&
      userProvidedParams[paramKey] !== null &&
      userProvidedParams[paramKey] !== ''
    ) {
      delete filteredProperties[paramKey]
      const reqIndex = filteredRequired.indexOf(paramKey)
      if (reqIndex > -1) {
        filteredRequired.splice(reqIndex, 1)
      }
    }
  })

  return {
    ...originalSchema,
    properties: filteredProperties,
    required: filteredRequired,
  }
}

/**
 * Validates that all required parameters are provided
 */
export function validateToolParameters(
  toolConfig: ToolConfig,
  finalParams: Record<string, unknown>
): ValidationResult {
  const requiredParams = Object.entries(toolConfig.params)
    .filter(([_, param]) => param.required)
    .map(([paramId]) => paramId)

  const missingParams = requiredParams.filter(
    (paramId) =>
      finalParams[paramId] === undefined ||
      finalParams[paramId] === null ||
      finalParams[paramId] === ''
  )

  return {
    valid: missingParams.length === 0,
    missingParams,
  }
}

/**
 * Helper to check if a parameter should be treated as a password field
 */
export function isPasswordParameter(paramId: string): boolean {
  const passwordFields = [
    'password',
    'apiKey',
    'token',
    'secret',
    'key',
    'credential',
    'accessToken',
    'refreshToken',
    'botToken',
    'authToken',
  ]

  return passwordFields.some((field) => paramId.toLowerCase().includes(field.toLowerCase()))
}

/**
 * Formats parameter IDs into human-readable labels
 */
export function formatParameterLabel(paramId: string): string {
  // Special cases
  if (paramId === 'apiKey') return 'API Key'
  if (paramId === 'apiVersion') return 'API Version'
  if (paramId === 'accessToken') return 'Access Token'
  if (paramId === 'refreshToken') return 'Refresh Token'
  if (paramId === 'botToken') return 'Bot Token'

  // Handle underscore and hyphen separated words
  if (paramId.includes('_') || paramId.includes('-')) {
    return paramId
      .split(/[-_]/)
      .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
      .join(' ')
  }

  // Handle single character parameters
  if (paramId.length === 1) return paramId.toUpperCase()

  // Handle camelCase
  if (/[A-Z]/.test(paramId)) {
    const result = paramId.replace(/([A-Z])/g, ' $1')
    return (
      result.charAt(0).toUpperCase() +
      result
        .slice(1)
        .replace(/ Api/g, ' API')
        .replace(/ Id/g, ' ID')
        .replace(/ Url/g, ' URL')
        .replace(/ Uri/g, ' URI')
        .replace(/ Ui/g, ' UI')
    )
  }

  // Simple case - just capitalize first letter
  return paramId.charAt(0).toUpperCase() + paramId.slice(1)
}
